Skip to content

feat(analytics): count anchor opens via card click + fix modal back-button#565

Merged
rdmueller merged 1 commit into
LLM-Coding:mainfrom
raifdmueller:feat/anchor-route-on-card-click
Jun 2, 2026
Merged

feat(analytics): count anchor opens via card click + fix modal back-button#565
rdmueller merged 1 commit into
LLM-Coding:mainfrom
raifdmueller:feat/anchor-route-on-card-click

Conversation

@raifdmueller
Copy link
Copy Markdown
Contributor

@raifdmueller raifdmueller commented Jun 2, 2026

Why

Follow-up to #562. Opening an anchor by clicking a card — the most common interaction — was not counted: handleAnchorSelection called showAnchorDetails() directly without changing the route, so handleRoute/pageview tracking never ran. Only deep-links and in-modal cross-references were counted, making the per-anchor stats nearly empty.

What

  • Card click now routes through navigate('/anchor/:id'). The URL reflects the open anchor (deep-linkable, shareable, Back closes it) and the router records the pageview → real per-anchor view counts.
  • Fixes a latent bug this would otherwise make the norm: leaving an anchor route (Back/forward or in-app navigation) left the modal stranded as an overlay over the page. handleRoute now closes an open anchor modal whenever a non-anchor route resolves (on a non-anchor route closeModal() doesn't touch the URL).

Privacy unchanged: path only, no query, no PII.

Verification (preview, real card click)

Clicked the first catalog card (4mat):

  • URL → /Semantic-Anchors/anchor/4mat, modal open
  • GoatCounter counted { path: "/Semantic-Anchors/anchor/4mat", title: "4mat — Semantic Anchors" }
  • Back → URL /Semantic-Anchors/, modal closed (previously stayed open)

All 98 unit tests pass; lint + prettier clean.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Neue Funktionen
    • Ankerpunkte können jetzt direkt über URLs aufgerufen und verlinkt werden.
    • Verbesserte Navigation: Das Ankerfenster wird beim Wechsel zu anderen Seiten automatisch geschlossen.

…ute change

Clicking an anchor card opened the modal by calling showAnchorDetails
directly, without changing the route — so the most common way to open an
anchor was never counted (only deep-links and in-modal cross-refs were).
Route card clicks through navigate('/anchor/:id') instead: the URL now
reflects the open anchor (deep-linkable, shareable) and the router records
it as a pageview, giving real per-anchor view counts.

Also fixes a latent bug this surfaces: leaving an anchor route (Back/forward
or in-app nav) left the modal stranded as an overlay over the page. The
router now closes an open anchor modal whenever a non-anchor route resolves;
on a non-anchor route closeModal() doesn't touch the URL.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 2, 2026

Review Change Stack

Caution

Review failed

Pull request was closed or merged during review

Walkthrough

Der PR verbindet die Anker-Öffnung mit Router-Navigation: handleAnchorSelection in main.js leitet zu /anchor/:id um, statt die Modal direkt aufzurufen. Die Router-Logik erhält eine neue Modal-Cleanup-Funktion, die beim Wechsel jeder Route eine offene Anker-Modal zuverlässig schließt.

Changes

Anker-Routing und Modal-Lifecycle

Layer / File(s) Summary
Modal-Lifecycle bei Routenänderung
website/src/utils/router.js
Neue interne Funktion closeOpenAnchorModal() prüft Sichtbarkeit des #anchor-modal-Elements und schließt es per dynamischem Import; diese wird zu Beginn von handleRoute() aufgerufen, um beim Verlassen von /anchor/:id (via Back/Forward oder App-Navigation) keine Modal-Overlay zurückzulassen.
Anker-Auswahl via Router-Navigation
website/src/main.js
Anker-Auswahl nutzt navigate(\/anchor/${anchorId}`)stattshowAnchorDetails(anchorId)` direkt; kommentierte Erklärung dokumentiert Deep-Linkbarkeit und Bindung des Zustands an Router/Pageview-Lifecycle.

Sequence Diagram

sequenceDiagram
  participant User
  participant handleAnchorSelection
  participant navigate as Router/<br>navigate()
  participant handleRoute
  participant closeOpenAnchorModal
  participant AnchorModal as `#anchor-modal`<br>Element

  User->>handleAnchorSelection: Click anchor
  handleAnchorSelection->>navigate: navigate(/anchor/:id)
  navigate->>handleRoute: Route change
  handleRoute->>closeOpenAnchorModal: closeOpenAnchorModal()
  closeOpenAnchorModal->>AnchorModal: Element present?
  alt Modal visible
    closeOpenAnchorModal->>AnchorModal: import anchor-modal.js
    closeOpenAnchorModal->>AnchorModal: closeModal()
  end
  handleRoute->>handleRoute: Process route
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • LLM-Coding/Semantic-Anchors#562: Modifiziert ebenfalls handleRoute()-Anker-Behandlung in router.js — diese PR schließt die Modal beim Verlassen von /anchor/:id, während #562 GoatCounter-Pageview-Tracking für dieselbe Route-Navigation hinzufügt.
  • LLM-Coding/Semantic-Anchors#198: Überschneidung bei Anker-Modal-/Router-Lifecycle um #/anchor/:id Navigation — diese PR schaltet Anker-Öffnung zu navigate('/anchor/:id') und schließt Modal bei Route-Änderung, während #198 Modal so anpasst, dass sie die aktuelle Seite überlagert und zur vorherigen Route zurückkehrt.
  • LLM-Coding/Semantic-Anchors#320: Modifiziert Anker-Modal-Flow — diese PR schaltet "Anker öffnen" zu /anchor/:id-Route-Navigation und fügt Auto-Closing beim Verlassen dieser Route hinzu, während #320 Anker-Modal-UI für jüngste Kommentare anpasst, was korrekte Modal-Anzeige/Verbergung voraussetzt.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Der PR-Titel bezieht sich auf die Kernänderungen: Zählung von Anker-Öffnungen über Kartenkicks und Behebung des Modal-Back-Button-Verhaltens, was mit den gezeigten Codeänderungen übereinstimmt.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

Tools execution failed with the following error:

Failed to run tools: 13 INTERNAL: Received RST_STREAM with code 2 (Internal server error)


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@rdmueller rdmueller merged commit 55ce9e7 into LLM-Coding:main Jun 2, 2026
5 of 7 checks passed
rdmueller pushed a commit that referenced this pull request Jun 2, 2026
Sub-anchor links render as <a href="#" data-sub-anchor>, which were also
matched by the AsciiDoc cross-reference handler (a[href^="#"]). Clicking a
sub-anchor therefore fired both its own handler AND a bogus
navigate('/anchor/') with an empty id. Harmless before, but since the
modal now closes when a non-anchor route resolves (#565), handleRoute
normalizes '/anchor/' → '/anchor' (not an anchor route) and closed the
umbrella modal — breaking the "navigate to sub-anchor / show back button"
E2E tests.

Exclude data-sub-anchor links from the cross-reference handler and ignore
empty ids, so sub-anchor clicks only load the sub-anchor and the modal +
back button stay put.

Verified: full E2E suite (35) green locally, incl. the two umbrella tests
that regressed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
raifdmueller added a commit to raifdmueller/Semantic-Anchors that referenced this pull request Jun 2, 2026
document.referrer is fixed for the whole SPA session, so every in-app
pageview we send (LLM-Coding#562/LLM-Coding#565) re-credited the entry referrer — e.g. a single
visitor arriving from heise and browsing 20 anchors made heise's referrer
list show all 20 paths. Report the referrer only on the initial load (handled
by count.js); client-side route changes now send an empty referrer via a
callback. External referrers are credited to the landing page only, matching
the intuitive "where did this visit come from" meaning.

Verified with GoatCounter's get_data: with document.referrer = heise, the
landing keeps r="https://www.heise.de" while an SPA anchor view yields r="".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants